library(dplyr)

1 Introduction

Again, this workshop borrows heavily from ModernDive, a textbook for learning to perform statistical analysis in R. I highly recommend at least skimming it, or referring back to it as necessary. You can get a deeper understanding of modeling in R by reading Hadley’s R for Data Science chapters on modeling, here. We previously saw how to use lm to do regression analysis in R and today we’ll cover sampling/bootstrapping and hypothesis testing in R.

1.1 Learning Objectives

  1. We’ll cover bootstrapping and confidence intervals (Modern Dive chapters 7 and 8)
  2. We’ll also get familiar with hypothesis testing (Modern Dive chapter 9)

2 Sampling

Sampling is at the heart of bootstrapping and generating confidence intervals. We’ve already seen how we can randomly sample a vector in R by using the sample function, and today we’ll use it to do bootstrapping and get estimates on population measures.

As you no doubt know, we use statistical analysis to estimate the value of some measurement of some population (population parameter) when we only have one or a few smaller samples to work with (since it’s difficult or impossible to collect data on the entire population). However, we’ll begin by assuming we have information about the entire population to demonstrate its utility.

The classical example is to consider a big bowl of colored balls.

We’ll use our R expertise to represent this bowl of balls as a vector of 0’s (white balls) or 1’s (red balls).

Your task: Create balls_population, a vector of length 500 to represent a bowl of 500 colored balls. Initialize it with 300 white balls (0’s) and 200 red balls (1’s) that are randomly shuffled throughout the vector. Hint: useful functions include rep and sample.

# TODO Your code here
balls_population <- c(rep(0, 300), rep(1, 200)) %>% sample

In this case, we know that the ratio of white to red balls is 3:2 (ie. 60% of the balls are white), but what if we weren’t able to count all the ball?

Your task: Get an estimate on the fraction of white in the bowl by extracting 50 random balls from balls_population and computing which fraction of them are white (0).

# TODO Your code here
mSample <- sample(balls_population, 50)

sum(mSample == 0) / length(mSample)

Any given measurement is, by itself, okay, but it’s not a reliable estimate of the true population parameter. Every time we randomly select balls, it’s subject to some variation. If we do this process of randomly selecting balls and computing the fraction, however, we can start to get a better idea of the real value.

Your task: Do this sampling experiment 1000 times by:

  1. Making a lapply function call that goes through the numbers 1-1000
  2. Inside of each call, sample 50 balls and compute the fraction of them that are white.
  3. Use unlist on the result to turn the list of experiments into a vector
# TODO Your code here
estimates <- lapply(1:1000, function(x) {
  mSample <- sample(balls_population, 50)
  sum(mSample == 0) / length(mSample)
}) %>% unlist

We can visualize our estimates in a histogram using {ggplot2} by turning our vector into a data.frame and plotting.

Your task: Plot a histogram or a density plot of your 1000 estimates on the ratio of white balls.

# TODO Your code here
data.frame(estimate = estimates) %>%
  ggplot(aes(estimate)) + geom_histogram() +
  theme_classic() + labs(x = 'Percentage of white balls', y = 'Count') +
  scale_x_continuous(expand = c(0, 0)) + # Get rid of the axis padding
  scale_y_continuous(expand = c(0, 0))

We can see that it’s roughly normal and centers around the true fraction, 60%.

3 Bootstrapping

What if we didn’t have the whole population to work with? Can we still make good guesses? We’ll transition from base R to using the package {infer} to do some of our analyses - it’s what UBC’s Masters of Data Science program uses and it interfaces nicely with {dplyr}.

install.packages('infer')
library(infer)

{infer} has the built in functions rep_sample_n which can reduce our typing overhead above, as well as functions to automatically calculate our sample estimates, generate confidence intervals and visualize results. To get a sense of this, we’ll first rewrite our above resampling code as an {infer} pipeline.

Your task: Turn your balls_population into a data.frame with one column: color, which is the ball color (either 1 or 0). We can then create an {infer} pipeline to do our repeated sampling by:

  1. First mutate-ing our color to turn it into a factor with labels white and red (unfortunately, {infer} doesn’t support numeric response variables)
  2. Use rep_sample_n to create 1000 random resamples (with replacement) of size 50
  3. Use specify to tell {infer} that color is our response variable and we want to calculate the fraction of white balls (ie. the “successful” discovery is “white”).
  4. Use calculate to calculate the proportion of balls that were “successful”
# TODO Your code here
balls_population <- data.frame(color = balls_population) %>%
  mutate(color = factor(color, levels = 0:1, labels = c('white', 'red')))

balls_population %>%
  rep_sample_n(50, TRUE, 1000) %>%
  specify(response = color, success = 'white') %>%
  calculate('prop')

Now that we’re familiar with the verbs available in {infer}, let’s use it to do our bootstrapping.

Bootstrapping is the process of making repeated estimates using our single sample (a representative example of our population) to try to extract more information about the population as a whole. We’ll first take a representative sample of our balls_population to simulate the idea of only having a smaller sample.

Your task: Take a sample of 100 rows of balls_population using {dplyr}’s slice_sample. Save this to balls_sample.

# TODO Your code here
balls_sample <- slice_sample(balls_population, n = 100)

Now we can perform bootstrapping by repeatedly drawing a bootstrap sample (ie. a sample of our sample, with replacement) from balls_sample.

Your task: Bootstrap over your balls_sample to create 1000 bootstrap estimates of the fraction of balls that are white by:

  1. First specifying your response and success
  2. Then generate-ing 1000 bootstrap replicates, which are resamplings of the same size of your sample, but with replacement
  3. And then calculate-ing the proportion of ball that are white.
# TODO Your code here
balls_sample %>%
  specify(response = color, success = 'white') %>%
  generate(1000, type = 'bootstrap') %>%
  calculate('prop')

3.1 Confidence Intervals

So what is the point of doing this repeated resampling? Well, it allows us to generate a range of possible values for our sample statistic, which we can use to make inferences about the population (which we were unable to get access to for financial/logistical reasons). We can quantify the range in which the true population parameter lies with a confidence interval (typically 95%).

Again, {infer} provides a very simply way to do this by simply chaining on a call to get_confidence_interval.

Your task: Calculate a 95% bootstrap confidence interval using your bootstrap distribution, which you calculated above.

# TODO Your code here
balls_sample %>%
  specify(response = color, success = 'white') %>%
  generate(1000, type = 'bootstrap') %>%
  calculate('prop') %>%
  get_confidence_interval()

# If we wanted to do this in base R, we could have used the quantile function to calculate which data is contained within the middle 2.5% and 97.5% (ie. quantile(DATA, c(0.025, 0.975)))

{infer} also has a method, visualize, which generates a histogram of your bootstrap distributions for you to inspect, which you can also shade with a confidence interval (shade_confidence_interval)

balls_sample %>%
  specify(...) %>%
  generate(...) %>%
  calculate(...) %>% {
    visualize(.) + shade_confidence_interval(get_confidence_interval(.))
  }

4 Hypothesis Testing

Hypothesis testing allows us to state whether or not there is sufficient evidence to suggest a statistically significant deviation from a null hypothesis. In other words, it allows us to make reasoned guesses about whether our data diverges from a given “uninteresting” model.

We can again do this kind of analysis again using {infer} using the same kind of pipeline as before to do a permutation test. Intuitively, we first specify the variables we’re interested in studying, hypothesize what the relationship would look like in the uninteresting (null) model, generate our permutations to create a null model, calculate our test statistics and then use these to determine how surprising it is to see our measurement.

I strongly recommend checking out this website for a visual and intuitive example of how this process works. We’ll work through the example there, here in R.

First, we’ll start with our sample of alpacas:

alpacas <- data.frame(ID = LETTERS[1:24],
                      group = rep(c('treatment', 'control'), each = 12),
                      wool_quality = c(7.7, 8.3, 7.2, 8.3, 7.4, 4.8, 4.1, 6.2, 4.4, 7.1, 4.3, 5.8, 4.6, 4.2, 4.6, 3.9, 5.6, 4.4, 4.1, 2.8, 3.8, 5.4, 5.1, 5.8))

We can first eyeball the values to see if there’s a perceivable difference in wool quality following shampoo treatment:

Your task: Make a boxplot that depicts the wool quality per experimental group of alpacas.

# TODO Your code here
alpacas %>%
  ggplot(aes(group, wool_quality, fill = group)) +
  geom_boxplot() +
  theme_classic() + theme(legend.position = 'none') +
  labs(x = 'Treatment Group', y = 'Wool Quality')

But to formally test whether the perceived difference is statistically significant or not, we should do a statistical test. Formally, we want to test to see if the average wool quality of the treated alpacas is higher than the wool quality of the control group. So our null hypothesis is \(H_0: \text{mean(wool_quality}_{control}\text) \geq \text{mean(wool_quality}_{treatment}\text)\), or equivalently, \(H_0: \text{mean(wool_quality}_{control}\text) - \text{mean(wool_quality}_{treatment}\text) \geq 0\). Our observed test statistic is first computed:

Your task: Calculate test_statistic: the difference between the mean wool quality of the treatment and control groups.

# TODO Your code here
test_statistic <- alpacas %>%
  group_by(group) %>%
  summarize(mean_wool_quality = mean(wool_quality)) %>%
  pull(mean_wool_quality) %>%
  diff

We’ll now follow our intuitive workflow to decide if our apparent difference is significant or not.

As a reminder, we first specify the variables we’re interested in studying, hypothesize what the relationship would look like in the uninteresting (null) model, generate our permutations to create a null model, calculate our sample statistics and then use these to determine how surprising it is to see our measurement. We already have a hypothesis formulated and an idea of how to calculate our sample statistics, so we should have no issues.

Your task: Implement the workflow above.

  1. Using alpacas, set up an {infer} pipeline by specifying the variables as a formula. Recall from last time how we write formulas with the response on the left of a tilde (~) and the explanatory variables on the right.
  2. Then hypothesize that the two variables you specified are independent
  3. generate 1000 permutations under the null model
  4. calculate a sample statistic for each permutation, which is the difference in group means (see ?calculate if confused)
  5. visualize the resulting distribution of test statistics
# TODO Your code here
alpacas %>%
  specify(wool_quality ~ group) %>%
  hypothesize('independence') %>%
  generate(1000, 'permute') %>%
  calculate('diff in means', order = c('treatment', 'control')) %>% {
    visualize(.) +
      shade_confidence_interval(get_confidence_interval(.)) +
      geom_vline(xintercept = test_statistic, color = 'red')
  }

This visualization can show us that it is unlikely to receive a test statistic at least as extreme as what we saw by chance, or in other words, there’s a low probability that the observed difference in wool quality is due to chance. We can quantify this with a p-value by using {infer}’s get_p_value function. Since our hypothesis is directional, we need to specify the tail we care about.

Your task: Instead of visualizing the distribution above, calculate a p-value for your observed test_statistic.

# TODO Your code here
alpacas %>%
  specify(wool_quality ~ group) %>%
  hypothesize('independence') %>%
  generate(1000, 'permute') %>%
  calculate('diff in means', order = c('treatment', 'control')) %>%
  get_p_value(test_statistic, 'right')

Of course, there are a large number of other statistical tests you can perform in R that aren’t permutation tests, including anova, chisq.test, fisher.test, t.test and etc. If you know the test flavor you want to run, they’re relatively straightforward to implement.

LS0tDQp0aXRsZTogIldvcmtzaG9wIDkiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiAiZmxhdGx5Ig0KICAgIGNzczogInd3dy9jc3Mvc3R5bGUuY3NzIg0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KYGBgDQoNCiMgSW50cm9kdWN0aW9uDQoNCkFnYWluLCB0aGlzIHdvcmtzaG9wIGJvcnJvd3MgaGVhdmlseSBmcm9tIFtNb2Rlcm5EaXZlXShodHRwczovL21vZGVybmRpdmUuY29tKSwgYSB0ZXh0Ym9vayBmb3IgbGVhcm5pbmcgdG8gcGVyZm9ybSBzdGF0aXN0aWNhbCBhbmFseXNpcyBpbiBSLiBJIGhpZ2hseSByZWNvbW1lbmQgYXQgbGVhc3Qgc2tpbW1pbmcgaXQsIG9yIHJlZmVycmluZyBiYWNrIHRvIGl0IGFzIG5lY2Vzc2FyeS4gWW91IGNhbiBnZXQgYSBkZWVwZXIgdW5kZXJzdGFuZGluZyBvZiBtb2RlbGluZyBpbiBSIGJ5IHJlYWRpbmcgSGFkbGV5J3MgUiBmb3IgRGF0YSBTY2llbmNlIGNoYXB0ZXJzIG9uIG1vZGVsaW5nLCBbaGVyZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9tb2RlbC1pbnRyby5odG1sKS4gV2UgcHJldmlvdXNseSBzYXcgaG93IHRvIHVzZSBgbG1gIHRvIGRvIHJlZ3Jlc3Npb24gYW5hbHlzaXMgaW4gUiBhbmQgdG9kYXkgd2UnbGwgY292ZXIgc2FtcGxpbmcvYm9vdHN0cmFwcGluZyBhbmQgaHlwb3RoZXNpcyB0ZXN0aW5nIGluIFIuDQoNCiMjIExlYXJuaW5nIE9iamVjdGl2ZXMNCg0KMS4gIFdlJ2xsIGNvdmVyIGJvb3RzdHJhcHBpbmcgYW5kIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIChNb2Rlcm4gRGl2ZSBjaGFwdGVycyA3IGFuZCA4KQ0KMi4gIFdlJ2xsIGFsc28gZ2V0IGZhbWlsaWFyIHdpdGggaHlwb3RoZXNpcyB0ZXN0aW5nIChNb2Rlcm4gRGl2ZSBjaGFwdGVyIDkpDQoNCiMgU2FtcGxpbmcNCg0KU2FtcGxpbmcgaXMgYXQgdGhlIGhlYXJ0IG9mIGJvb3RzdHJhcHBpbmcgYW5kIGdlbmVyYXRpbmcgY29uZmlkZW5jZSBpbnRlcnZhbHMuIFdlJ3ZlIGFscmVhZHkgc2VlbiBob3cgd2UgY2FuIHJhbmRvbWx5IHNhbXBsZSBhIHZlY3RvciBpbiBSIGJ5IHVzaW5nIHRoZSBgc2FtcGxlYCBmdW5jdGlvbiwgYW5kIHRvZGF5IHdlJ2xsIHVzZSBpdCB0byBkbyBib290c3RyYXBwaW5nIGFuZCBnZXQgZXN0aW1hdGVzIG9uIHBvcHVsYXRpb24gbWVhc3VyZXMuDQoNCkFzIHlvdSBubyBkb3VidCBrbm93LCB3ZSB1c2Ugc3RhdGlzdGljYWwgYW5hbHlzaXMgdG8gZXN0aW1hdGUgdGhlIHZhbHVlIG9mIHNvbWUgbWVhc3VyZW1lbnQgb2Ygc29tZSBwb3B1bGF0aW9uIChwb3B1bGF0aW9uIHBhcmFtZXRlcikgd2hlbiB3ZSBvbmx5IGhhdmUgb25lIG9yIGEgZmV3IHNtYWxsZXIgc2FtcGxlcyB0byB3b3JrIHdpdGggKHNpbmNlIGl0J3MgZGlmZmljdWx0IG9yIGltcG9zc2libGUgdG8gY29sbGVjdCBkYXRhIG9uIHRoZSBlbnRpcmUgcG9wdWxhdGlvbikuIEhvd2V2ZXIsIHdlJ2xsIGJlZ2luIGJ5IGFzc3VtaW5nIHdlIGhhdmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGVudGlyZSBwb3B1bGF0aW9uIHRvIGRlbW9uc3RyYXRlIGl0cyB1dGlsaXR5Lg0KDQpUaGUgY2xhc3NpY2FsIGV4YW1wbGUgaXMgdG8gY29uc2lkZXIgYSBiaWcgYm93bCBvZiBjb2xvcmVkIGJhbGxzLg0KDQpbIVtdKGh0dHBzOi8vZDMzd3VicmZraTBsNjguY2xvdWRmcm9udC5uZXQvMDg2OGM2NGViMjk5OGZiZTNlM2JmY2NmN2ZmNTVlNzQ2MzQxYmFmMy9lZWFiMS9pbWFnZXMvc2FtcGxpbmcvYmFsbHMvc2FtcGxpbmdfYm93bF8xLmpwZyldKGh0dHBzOi8vbW9kZXJuZGl2ZS5jb20vNy1zYW1wbGluZy5odG1sKQ0KDQpXZSdsbCB1c2Ugb3VyIFIgZXhwZXJ0aXNlIHRvIHJlcHJlc2VudCB0aGlzIGJvd2wgb2YgYmFsbHMgYXMgYSB2ZWN0b3Igb2YgMCdzICh3aGl0ZSBiYWxscykgb3IgMSdzIChyZWQgYmFsbHMpLg0KDQoqKllvdXIgdGFzazoqKiBDcmVhdGUgYGJhbGxzX3BvcHVsYXRpb25gLCBhIHZlY3RvciBvZiBsZW5ndGggNTAwIHRvIHJlcHJlc2VudCBhIGJvd2wgb2YgNTAwIGNvbG9yZWQgYmFsbHMuIEluaXRpYWxpemUgaXQgd2l0aCAzMDAgd2hpdGUgYmFsbHMgKDAncykgYW5kIDIwMCByZWQgYmFsbHMgKDEncykgdGhhdCBhcmUgcmFuZG9tbHkgc2h1ZmZsZWQgdGhyb3VnaG91dCB0aGUgdmVjdG9yLiAqSGludDogdXNlZnVsIGZ1bmN0aW9ucyBpbmNsdWRlKiBgcmVwYCBhbmQgYHNhbXBsZWAuDQoNCmBgYHtyfQ0KIyBUT0RPIFlvdXIgY29kZSBoZXJlDQpgYGANCg0KOjo6IHsuc3BvaWxlcn0NCmBgYHtyIGV2YWw9RkFMU0V9DQpiYWxsc19wb3B1bGF0aW9uIDwtIGMocmVwKDAsIDMwMCksIHJlcCgxLCAyMDApKSAlPiUgc2FtcGxlDQpgYGANCjo6Og0KDQpJbiB0aGlzIGNhc2UsIHdlIGtub3cgdGhhdCB0aGUgcmF0aW8gb2Ygd2hpdGUgdG8gcmVkIGJhbGxzIGlzIDM6MiAoaWUuIDYwJSBvZiB0aGUgYmFsbHMgYXJlIHdoaXRlKSwgYnV0IHdoYXQgaWYgd2Ugd2VyZW4ndCBhYmxlIHRvIGNvdW50IGFsbCB0aGUgYmFsbD8NCg0KKipZb3VyIHRhc2s6KiogR2V0IGFuIGVzdGltYXRlIG9uIHRoZSBmcmFjdGlvbiBvZiB3aGl0ZSBpbiB0aGUgYm93bCBieSBleHRyYWN0aW5nIDUwIHJhbmRvbSBiYWxscyBmcm9tIGBiYWxsc19wb3B1bGF0aW9uYCBhbmQgY29tcHV0aW5nIHdoaWNoIGZyYWN0aW9uIG9mIHRoZW0gYXJlIHdoaXRlICgwKS4NCg0KYGBge3J9DQojIFRPRE8gWW91ciBjb2RlIGhlcmUNCmBgYA0KDQo6Ojogey5zcG9pbGVyfQ0KYGBge3IgZXZhbD1GQUxTRX0NCm1TYW1wbGUgPC0gc2FtcGxlKGJhbGxzX3BvcHVsYXRpb24sIDUwKQ0KDQpzdW0obVNhbXBsZSA9PSAwKSAvIGxlbmd0aChtU2FtcGxlKQ0KYGBgDQo6OjoNCg0KQW55IGdpdmVuIG1lYXN1cmVtZW50IGlzLCBieSBpdHNlbGYsIG9rYXksIGJ1dCBpdCdzIG5vdCBhIHJlbGlhYmxlIGVzdGltYXRlIG9mIHRoZSB0cnVlIHBvcHVsYXRpb24gcGFyYW1ldGVyLiBFdmVyeSB0aW1lIHdlIHJhbmRvbWx5IHNlbGVjdCBiYWxscywgaXQncyBzdWJqZWN0IHRvIHNvbWUgdmFyaWF0aW9uLiBJZiB3ZSBkbyB0aGlzIHByb2Nlc3Mgb2YgcmFuZG9tbHkgc2VsZWN0aW5nIGJhbGxzIGFuZCBjb21wdXRpbmcgdGhlIGZyYWN0aW9uLCBob3dldmVyLCB3ZSBjYW4gc3RhcnQgdG8gZ2V0IGEgYmV0dGVyIGlkZWEgb2YgdGhlIHJlYWwgdmFsdWUuDQoNCioqWW91ciB0YXNrOioqIERvIHRoaXMgc2FtcGxpbmcgZXhwZXJpbWVudCAxMDAwIHRpbWVzIGJ5Og0KDQoxLiAgTWFraW5nIGEgYGxhcHBseWAgZnVuY3Rpb24gY2FsbCB0aGF0IGdvZXMgdGhyb3VnaCB0aGUgbnVtYmVycyAxLTEwMDANCjIuICBJbnNpZGUgb2YgZWFjaCBjYWxsLCBzYW1wbGUgNTAgYmFsbHMgYW5kIGNvbXB1dGUgdGhlIGZyYWN0aW9uIG9mIHRoZW0gdGhhdCBhcmUgd2hpdGUuDQozLiAgVXNlIGB1bmxpc3RgIG9uIHRoZSByZXN1bHQgdG8gdHVybiB0aGUgbGlzdCBvZiBleHBlcmltZW50cyBpbnRvIGEgdmVjdG9yDQoNCmBgYHtyfQ0KIyBUT0RPIFlvdXIgY29kZSBoZXJlDQpgYGANCg0KOjo6IHsuc3BvaWxlcn0NCmBgYHtyIGV2YWw9RkFMU0V9DQplc3RpbWF0ZXMgPC0gbGFwcGx5KDE6MTAwMCwgZnVuY3Rpb24oeCkgew0KICBtU2FtcGxlIDwtIHNhbXBsZShiYWxsc19wb3B1bGF0aW9uLCA1MCkNCiAgc3VtKG1TYW1wbGUgPT0gMCkgLyBsZW5ndGgobVNhbXBsZSkNCn0pICU+JSB1bmxpc3QNCmBgYA0KOjo6DQoNCldlIGNhbiB2aXN1YWxpemUgb3VyIGVzdGltYXRlcyBpbiBhIGhpc3RvZ3JhbSB1c2luZyB7Z2dwbG90Mn0gYnkgdHVybmluZyBvdXIgdmVjdG9yIGludG8gYSBkYXRhLmZyYW1lIGFuZCBwbG90dGluZy4NCg0KKipZb3VyIHRhc2s6KiogUGxvdCBhIGhpc3RvZ3JhbSBvciBhIGRlbnNpdHkgcGxvdCBvZiB5b3VyIDEwMDAgZXN0aW1hdGVzIG9uIHRoZSByYXRpbyBvZiB3aGl0ZSBiYWxscy4NCg0KYGBge3J9DQojIFRPRE8gWW91ciBjb2RlIGhlcmUNCmBgYA0KDQo6Ojogey5zcG9pbGVyfQ0KYGBge3IgZXZhbD1GQUxTRX0NCmRhdGEuZnJhbWUoZXN0aW1hdGUgPSBlc3RpbWF0ZXMpICU+JQ0KICBnZ3Bsb3QoYWVzKGVzdGltYXRlKSkgKyBnZW9tX2hpc3RvZ3JhbSgpICsNCiAgdGhlbWVfY2xhc3NpYygpICsgbGFicyh4ID0gJ1BlcmNlbnRhZ2Ugb2Ygd2hpdGUgYmFsbHMnLCB5ID0gJ0NvdW50JykgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKyAjIEdldCByaWQgb2YgdGhlIGF4aXMgcGFkZGluZw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkNCmBgYA0KOjo6DQoNCldlIGNhbiBzZWUgdGhhdCBpdCdzIHJvdWdobHkgbm9ybWFsIGFuZCBjZW50ZXJzIGFyb3VuZCB0aGUgdHJ1ZSBmcmFjdGlvbiwgNjAlLg0KDQojIEJvb3RzdHJhcHBpbmcNCg0KV2hhdCBpZiB3ZSBkaWRuJ3QgaGF2ZSB0aGUgd2hvbGUgcG9wdWxhdGlvbiB0byB3b3JrIHdpdGg/IENhbiB3ZSBzdGlsbCBtYWtlIGdvb2QgZ3Vlc3Nlcz8gV2UnbGwgdHJhbnNpdGlvbiBmcm9tIGJhc2UgUiB0byB1c2luZyB0aGUgcGFja2FnZSB7aW5mZXJ9IHRvIGRvIHNvbWUgb2Ygb3VyIGFuYWx5c2VzIC0gaXQncyB3aGF0IFVCQydzIE1hc3RlcnMgb2YgRGF0YSBTY2llbmNlIHByb2dyYW0gdXNlcyBhbmQgaXQgaW50ZXJmYWNlcyBuaWNlbHkgd2l0aCB7ZHBseXJ9Lg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygnaW5mZXInKQ0KbGlicmFyeShpbmZlcikNCmBgYA0KDQp7aW5mZXJ9IGhhcyB0aGUgYnVpbHQgaW4gZnVuY3Rpb25zIGByZXBfc2FtcGxlX25gIHdoaWNoIGNhbiByZWR1Y2Ugb3VyIHR5cGluZyBvdmVyaGVhZCBhYm92ZSwgYXMgd2VsbCBhcyBmdW5jdGlvbnMgdG8gYXV0b21hdGljYWxseSBjYWxjdWxhdGUgb3VyIHNhbXBsZSBlc3RpbWF0ZXMsIGdlbmVyYXRlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIGFuZCB2aXN1YWxpemUgcmVzdWx0cy4gVG8gZ2V0IGEgc2Vuc2Ugb2YgdGhpcywgd2UnbGwgZmlyc3QgcmV3cml0ZSBvdXIgYWJvdmUgcmVzYW1wbGluZyBjb2RlIGFzIGFuIHtpbmZlcn0gcGlwZWxpbmUuDQoNCioqWW91ciB0YXNrOioqIFR1cm4geW91ciBgYmFsbHNfcG9wdWxhdGlvbmAgaW50byBhIGRhdGEuZnJhbWUgd2l0aCBvbmUgY29sdW1uOiBgY29sb3JgLCB3aGljaCBpcyB0aGUgYmFsbCBjb2xvciAoZWl0aGVyIDEgb3IgMCkuIFdlIGNhbiB0aGVuIGNyZWF0ZSBhbiB7aW5mZXJ9IHBpcGVsaW5lIHRvIGRvIG91ciByZXBlYXRlZCBzYW1wbGluZyBieToNCg0KMS4gIEZpcnN0IGBtdXRhdGVgLWluZyBvdXIgY29sb3IgdG8gdHVybiBpdCBpbnRvIGEgZmFjdG9yIHdpdGggbGFiZWxzIGB3aGl0ZWAgYW5kIGByZWRgICh1bmZvcnR1bmF0ZWx5LCB7aW5mZXJ9IGRvZXNuJ3Qgc3VwcG9ydCBudW1lcmljIHJlc3BvbnNlIHZhcmlhYmxlcykNCjIuICBVc2UgYHJlcF9zYW1wbGVfbmAgdG8gY3JlYXRlIDEwMDAgcmFuZG9tIHJlc2FtcGxlcyAod2l0aCByZXBsYWNlbWVudCkgb2Ygc2l6ZSA1MA0KMy4gIFVzZSBgc3BlY2lmeWAgdG8gdGVsbCB7aW5mZXJ9IHRoYXQgYGNvbG9yYCBpcyBvdXIgcmVzcG9uc2UgdmFyaWFibGUgYW5kIHdlIHdhbnQgdG8gY2FsY3VsYXRlIHRoZSBmcmFjdGlvbiBvZiB3aGl0ZSBiYWxscyAoaWUuIHRoZSAic3VjY2Vzc2Z1bCIgZGlzY292ZXJ5IGlzICJ3aGl0ZSIpLg0KNC4gIFVzZSBgY2FsY3VsYXRlYCB0byBjYWxjdWxhdGUgdGhlIGBwcm9wYG9ydGlvbiBvZiBiYWxscyB0aGF0IHdlcmUgInN1Y2Nlc3NmdWwiDQoNCmBgYHtyfQ0KIyBUT0RPIFlvdXIgY29kZSBoZXJlDQpgYGANCg0KOjo6IHsuc3BvaWxlcn0NCmBgYHtyIGV2YWw9RkFMU0V9DQpiYWxsc19wb3B1bGF0aW9uIDwtIGRhdGEuZnJhbWUoY29sb3IgPSBiYWxsc19wb3B1bGF0aW9uKSAlPiUNCiAgbXV0YXRlKGNvbG9yID0gZmFjdG9yKGNvbG9yLCBsZXZlbHMgPSAwOjEsIGxhYmVscyA9IGMoJ3doaXRlJywgJ3JlZCcpKSkNCg0KYmFsbHNfcG9wdWxhdGlvbiAlPiUNCiAgcmVwX3NhbXBsZV9uKDUwLCBUUlVFLCAxMDAwKSAlPiUNCiAgc3BlY2lmeShyZXNwb25zZSA9IGNvbG9yLCBzdWNjZXNzID0gJ3doaXRlJykgJT4lDQogIGNhbGN1bGF0ZSgncHJvcCcpDQpgYGANCjo6Og0KDQpOb3cgdGhhdCB3ZSdyZSBmYW1pbGlhciB3aXRoIHRoZSB2ZXJicyBhdmFpbGFibGUgaW4ge2luZmVyfSwgbGV0J3MgdXNlIGl0IHRvIGRvIG91ciBib290c3RyYXBwaW5nLg0KDQpCb290c3RyYXBwaW5nIGlzIHRoZSBwcm9jZXNzIG9mIG1ha2luZyByZXBlYXRlZCBlc3RpbWF0ZXMgdXNpbmcgb3VyIHNpbmdsZSBzYW1wbGUgKGEgcmVwcmVzZW50YXRpdmUgZXhhbXBsZSBvZiBvdXIgcG9wdWxhdGlvbikgdG8gdHJ5IHRvIGV4dHJhY3QgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgcG9wdWxhdGlvbiBhcyBhIHdob2xlLiBXZSdsbCBmaXJzdCB0YWtlIGEgcmVwcmVzZW50YXRpdmUgc2FtcGxlIG9mIG91ciBgYmFsbHNfcG9wdWxhdGlvbmAgdG8gc2ltdWxhdGUgdGhlIGlkZWEgb2Ygb25seSBoYXZpbmcgYSBzbWFsbGVyIHNhbXBsZS4NCg0KKipZb3VyIHRhc2s6KiogVGFrZSBhIHNhbXBsZSBvZiAxMDAgcm93cyBvZiBgYmFsbHNfcG9wdWxhdGlvbmAgdXNpbmcge2RwbHlyfSdzIGBzbGljZV9zYW1wbGVgLiBTYXZlIHRoaXMgdG8gYGJhbGxzX3NhbXBsZWAuDQoNCmBgYHtyfQ0KIyBUT0RPIFlvdXIgY29kZSBoZXJlDQpgYGANCg0KOjo6IHsuc3BvaWxlcn0NCmBgYHtyIGV2YWw9RkFMU0V9DQpiYWxsc19zYW1wbGUgPC0gc2xpY2Vfc2FtcGxlKGJhbGxzX3BvcHVsYXRpb24sIG4gPSAxMDApDQpgYGANCjo6Og0KDQpOb3cgd2UgY2FuIHBlcmZvcm0gYm9vdHN0cmFwcGluZyBieSByZXBlYXRlZGx5IGRyYXdpbmcgYSBib290c3RyYXAgc2FtcGxlIChpZS4gYSBzYW1wbGUgb2Ygb3VyIHNhbXBsZSwgd2l0aCByZXBsYWNlbWVudCkgZnJvbSBgYmFsbHNfc2FtcGxlYC4NCg0KKipZb3VyIHRhc2s6KiogQm9vdHN0cmFwIG92ZXIgeW91ciBgYmFsbHNfc2FtcGxlYCB0byBjcmVhdGUgMTAwMCBib290c3RyYXAgZXN0aW1hdGVzIG9mIHRoZSBmcmFjdGlvbiBvZiBiYWxscyB0aGF0IGFyZSB3aGl0ZSBieToNCg0KMS4gIEZpcnN0IGBzcGVjaWZ5YGluZyB5b3VyIGByZXNwb25zZWAgYW5kIGBzdWNjZXNzYA0KMi4gIFRoZW4gYGdlbmVyYXRlYC1pbmcgMTAwMCBib290c3RyYXAgcmVwbGljYXRlcywgd2hpY2ggYXJlIHJlc2FtcGxpbmdzIG9mIHRoZSBzYW1lIHNpemUgb2YgeW91ciBzYW1wbGUsIGJ1dCB3aXRoIHJlcGxhY2VtZW50DQozLiAgQW5kIHRoZW4gYGNhbGN1bGF0ZWAtaW5nIHRoZSBwcm9wb3J0aW9uIG9mIGJhbGwgdGhhdCBhcmUgd2hpdGUuDQoNCmBgYHtyfQ0KIyBUT0RPIFlvdXIgY29kZSBoZXJlDQpgYGANCg0KOjo6IHsuc3BvaWxlcn0NCmBgYHtyIGV2YWw9RkFMU0V9DQpiYWxsc19zYW1wbGUgJT4lDQogIHNwZWNpZnkocmVzcG9uc2UgPSBjb2xvciwgc3VjY2VzcyA9ICd3aGl0ZScpICU+JQ0KICBnZW5lcmF0ZSgxMDAwLCB0eXBlID0gJ2Jvb3RzdHJhcCcpICU+JQ0KICBjYWxjdWxhdGUoJ3Byb3AnKQ0KYGBgDQo6OjoNCg0KIyMgQ29uZmlkZW5jZSBJbnRlcnZhbHMNCg0KU28gd2hhdCBpcyB0aGUgcG9pbnQgb2YgZG9pbmcgdGhpcyByZXBlYXRlZCByZXNhbXBsaW5nPyBXZWxsLCBpdCBhbGxvd3MgdXMgdG8gZ2VuZXJhdGUgYSByYW5nZSBvZiBwb3NzaWJsZSB2YWx1ZXMgZm9yIG91ciBzYW1wbGUgc3RhdGlzdGljLCB3aGljaCB3ZSBjYW4gdXNlIHRvIG1ha2UgaW5mZXJlbmNlcyBhYm91dCB0aGUgcG9wdWxhdGlvbiAod2hpY2ggd2Ugd2VyZSB1bmFibGUgdG8gZ2V0IGFjY2VzcyB0byBmb3IgZmluYW5jaWFsL2xvZ2lzdGljYWwgcmVhc29ucykuIFdlIGNhbiBxdWFudGlmeSB0aGUgcmFuZ2UgaW4gd2hpY2ggdGhlIHRydWUgcG9wdWxhdGlvbiBwYXJhbWV0ZXIgbGllcyB3aXRoIGEgY29uZmlkZW5jZSBpbnRlcnZhbCAodHlwaWNhbGx5IDk1JSkuDQoNCkFnYWluLCB7aW5mZXJ9IHByb3ZpZGVzIGEgdmVyeSBzaW1wbHkgd2F5IHRvIGRvIHRoaXMgYnkgc2ltcGx5IGNoYWluaW5nIG9uIGEgY2FsbCB0byBgZ2V0X2NvbmZpZGVuY2VfaW50ZXJ2YWxgLg0KDQoqKllvdXIgdGFzazoqKiBDYWxjdWxhdGUgYSA5NSUgYm9vdHN0cmFwIGNvbmZpZGVuY2UgaW50ZXJ2YWwgdXNpbmcgeW91ciBib290c3RyYXAgZGlzdHJpYnV0aW9uLCB3aGljaCB5b3UgY2FsY3VsYXRlZCBhYm92ZS4NCg0KYGBge3J9DQojIFRPRE8gWW91ciBjb2RlIGhlcmUNCmBgYA0KDQo6Ojogey5zcG9pbGVyfQ0KYGBge3IgZXZhbD1GQUxTRX0NCmJhbGxzX3NhbXBsZSAlPiUNCiAgc3BlY2lmeShyZXNwb25zZSA9IGNvbG9yLCBzdWNjZXNzID0gJ3doaXRlJykgJT4lDQogIGdlbmVyYXRlKDEwMDAsIHR5cGUgPSAnYm9vdHN0cmFwJykgJT4lDQogIGNhbGN1bGF0ZSgncHJvcCcpICU+JQ0KICBnZXRfY29uZmlkZW5jZV9pbnRlcnZhbCgpDQoNCiMgSWYgd2Ugd2FudGVkIHRvIGRvIHRoaXMgaW4gYmFzZSBSLCB3ZSBjb3VsZCBoYXZlIHVzZWQgdGhlIHF1YW50aWxlIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB3aGljaCBkYXRhIGlzIGNvbnRhaW5lZCB3aXRoaW4gdGhlIG1pZGRsZSAyLjUlIGFuZCA5Ny41JSAoaWUuIHF1YW50aWxlKERBVEEsIGMoMC4wMjUsIDAuOTc1KSkpDQpgYGANCjo6Og0KDQp7aW5mZXJ9IGFsc28gaGFzIGEgbWV0aG9kLCBgdmlzdWFsaXplYCwgd2hpY2ggZ2VuZXJhdGVzIGEgaGlzdG9ncmFtIG9mIHlvdXIgYm9vdHN0cmFwIGRpc3RyaWJ1dGlvbnMgZm9yIHlvdSB0byBpbnNwZWN0LCB3aGljaCB5b3UgY2FuIGFsc28gc2hhZGUgd2l0aCBhIGNvbmZpZGVuY2UgaW50ZXJ2YWwgKGBzaGFkZV9jb25maWRlbmNlX2ludGVydmFsYCkNCg0KYGBge3IgZXZhbD1GQUxTRX0NCmJhbGxzX3NhbXBsZSAlPiUNCiAgc3BlY2lmeSguLi4pICU+JQ0KICBnZW5lcmF0ZSguLi4pICU+JQ0KICBjYWxjdWxhdGUoLi4uKSAlPiUgew0KICAgIHZpc3VhbGl6ZSguKSArIHNoYWRlX2NvbmZpZGVuY2VfaW50ZXJ2YWwoZ2V0X2NvbmZpZGVuY2VfaW50ZXJ2YWwoLikpDQogIH0NCmBgYA0KDQojIEh5cG90aGVzaXMgVGVzdGluZw0KDQpIeXBvdGhlc2lzIHRlc3RpbmcgYWxsb3dzIHVzIHRvIHN0YXRlIHdoZXRoZXIgb3Igbm90IHRoZXJlIGlzIHN1ZmZpY2llbnQgZXZpZGVuY2UgdG8gc3VnZ2VzdCBhIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZGV2aWF0aW9uIGZyb20gYSBudWxsIGh5cG90aGVzaXMuIEluIG90aGVyIHdvcmRzLCBpdCBhbGxvd3MgdXMgdG8gbWFrZSByZWFzb25lZCBndWVzc2VzIGFib3V0IHdoZXRoZXIgb3VyIGRhdGEgZGl2ZXJnZXMgZnJvbSBhIGdpdmVuICJ1bmludGVyZXN0aW5nIiBtb2RlbC4NCg0KV2UgY2FuIGFnYWluIGRvIHRoaXMga2luZCBvZiBhbmFseXNpcyBhZ2FpbiB1c2luZyB7aW5mZXJ9IHVzaW5nIHRoZSBzYW1lIGtpbmQgb2YgcGlwZWxpbmUgYXMgYmVmb3JlIHRvIGRvIGEgcGVybXV0YXRpb24gdGVzdC4gSW50dWl0aXZlbHksIHdlIGZpcnN0IGBzcGVjaWZ5YCB0aGUgdmFyaWFibGVzIHdlJ3JlIGludGVyZXN0ZWQgaW4gc3R1ZHlpbmcsIGBoeXBvdGhlc2l6ZWAgd2hhdCB0aGUgcmVsYXRpb25zaGlwIHdvdWxkIGxvb2sgbGlrZSBpbiB0aGUgdW5pbnRlcmVzdGluZyAobnVsbCkgbW9kZWwsIGBnZW5lcmF0ZWAgb3VyIHBlcm11dGF0aW9ucyB0byBjcmVhdGUgYSBudWxsIG1vZGVsLCBgY2FsY3VsYXRlYCBvdXIgdGVzdCBzdGF0aXN0aWNzIGFuZCB0aGVuIHVzZSB0aGVzZSB0byBkZXRlcm1pbmUgaG93IHN1cnByaXNpbmcgaXQgaXMgdG8gc2VlIG91ciBtZWFzdXJlbWVudC4NCg0KSSBzdHJvbmdseSByZWNvbW1lbmQgY2hlY2tpbmcgb3V0IFt0aGlzIHdlYnNpdGVdKGh0dHBzOi8vd3d3Lmp3aWxiZXIubWUvcGVybXV0YXRpb250ZXN0LykgZm9yIGEgdmlzdWFsIGFuZCBpbnR1aXRpdmUgZXhhbXBsZSBvZiBob3cgdGhpcyBwcm9jZXNzIHdvcmtzLiBXZSdsbCB3b3JrIHRocm91Z2ggdGhlIGV4YW1wbGUgdGhlcmUsIGhlcmUgaW4gUi4NCg0KRmlyc3QsIHdlJ2xsIHN0YXJ0IHdpdGggb3VyIHNhbXBsZSBvZiBhbHBhY2FzOg0KDQpgYGB7cn0NCmFscGFjYXMgPC0gZGF0YS5mcmFtZShJRCA9IExFVFRFUlNbMToyNF0sDQogICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSByZXAoYygndHJlYXRtZW50JywgJ2NvbnRyb2wnKSwgZWFjaCA9IDEyKSwNCiAgICAgICAgICAgICAgICAgICAgICB3b29sX3F1YWxpdHkgPSBjKDcuNywgOC4zLCA3LjIsIDguMywgNy40LCA0LjgsIDQuMSwgNi4yLCA0LjQsIDcuMSwgNC4zLCA1LjgsIDQuNiwgNC4yLCA0LjYsIDMuOSwgNS42LCA0LjQsIDQuMSwgMi44LCAzLjgsIDUuNCwgNS4xLCA1LjgpKQ0KYGBgDQoNCldlIGNhbiBmaXJzdCBleWViYWxsIHRoZSB2YWx1ZXMgdG8gc2VlIGlmIHRoZXJlJ3MgYSBwZXJjZWl2YWJsZSBkaWZmZXJlbmNlIGluIHdvb2wgcXVhbGl0eSBmb2xsb3dpbmcgc2hhbXBvbyB0cmVhdG1lbnQ6DQoNCioqWW91ciB0YXNrOioqIE1ha2UgYSBib3hwbG90IHRoYXQgZGVwaWN0cyB0aGUgd29vbCBxdWFsaXR5IHBlciBleHBlcmltZW50YWwgZ3JvdXAgb2YgYWxwYWNhcy4NCg0KYGBge3J9DQojIFRPRE8gWW91ciBjb2RlIGhlcmUNCmBgYA0KDQo6Ojogey5zcG9pbGVyfQ0KYGBge3IgZXZhbD1GQUxTRX0NCmFscGFjYXMgJT4lDQogIGdncGxvdChhZXMoZ3JvdXAsIHdvb2xfcXVhbGl0eSwgZmlsbCA9IGdyb3VwKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIHRoZW1lX2NsYXNzaWMoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKw0KICBsYWJzKHggPSAnVHJlYXRtZW50IEdyb3VwJywgeSA9ICdXb29sIFF1YWxpdHknKQ0KYGBgDQo6OjoNCg0KQnV0IHRvIGZvcm1hbGx5IHRlc3Qgd2hldGhlciB0aGUgcGVyY2VpdmVkIGRpZmZlcmVuY2UgaXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBvciBub3QsIHdlIHNob3VsZCBkbyBhIHN0YXRpc3RpY2FsIHRlc3QuIEZvcm1hbGx5LCB3ZSB3YW50IHRvIHRlc3QgdG8gc2VlIGlmIHRoZSBhdmVyYWdlIHdvb2wgcXVhbGl0eSBvZiB0aGUgdHJlYXRlZCBhbHBhY2FzIGlzIGhpZ2hlciB0aGFuIHRoZSB3b29sIHF1YWxpdHkgb2YgdGhlIGNvbnRyb2wgZ3JvdXAuIFNvIG91ciBudWxsIGh5cG90aGVzaXMgaXMgJEhfMDogXHRleHR7bWVhbih3b29sX3F1YWxpdHl9X3tjb250cm9sfVx0ZXh0KSBcZ2VxIFx0ZXh0e21lYW4od29vbF9xdWFsaXR5fV97dHJlYXRtZW50fVx0ZXh0KSQsIG9yIGVxdWl2YWxlbnRseSwgJEhfMDogXHRleHR7bWVhbih3b29sX3F1YWxpdHl9X3tjb250cm9sfVx0ZXh0KSAtIFx0ZXh0e21lYW4od29vbF9xdWFsaXR5fV97dHJlYXRtZW50fVx0ZXh0KSBcZ2VxIDAkLiBPdXIgb2JzZXJ2ZWQgdGVzdCBzdGF0aXN0aWMgaXMgZmlyc3QgY29tcHV0ZWQ6DQoNCioqWW91ciB0YXNrOioqIENhbGN1bGF0ZSBgdGVzdF9zdGF0aXN0aWNgOiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBtZWFuIHdvb2wgcXVhbGl0eSBvZiB0aGUgdHJlYXRtZW50IGFuZCBjb250cm9sIGdyb3Vwcy4NCg0KYGBge3J9DQojIFRPRE8gWW91ciBjb2RlIGhlcmUNCmBgYA0KDQo6Ojogey5zcG9pbGVyfQ0KYGBge3IgZXZhbD1GQUxTRX0NCnRlc3Rfc3RhdGlzdGljIDwtIGFscGFjYXMgJT4lDQogIGdyb3VwX2J5KGdyb3VwKSAlPiUNCiAgc3VtbWFyaXplKG1lYW5fd29vbF9xdWFsaXR5ID0gbWVhbih3b29sX3F1YWxpdHkpKSAlPiUNCiAgcHVsbChtZWFuX3dvb2xfcXVhbGl0eSkgJT4lDQogIGRpZmYNCmBgYA0KOjo6DQoNCldlJ2xsIG5vdyBmb2xsb3cgb3VyIGludHVpdGl2ZSB3b3JrZmxvdyB0byBkZWNpZGUgaWYgb3VyIGFwcGFyZW50IGRpZmZlcmVuY2UgaXMgc2lnbmlmaWNhbnQgb3Igbm90Lg0KDQpBcyBhIHJlbWluZGVyLCB3ZSBmaXJzdCBgc3BlY2lmeWAgdGhlIHZhcmlhYmxlcyB3ZSdyZSBpbnRlcmVzdGVkIGluIHN0dWR5aW5nLCBgaHlwb3RoZXNpemVgIHdoYXQgdGhlIHJlbGF0aW9uc2hpcCB3b3VsZCBsb29rIGxpa2UgaW4gdGhlIHVuaW50ZXJlc3RpbmcgKG51bGwpIG1vZGVsLCBgZ2VuZXJhdGVgIG91ciBwZXJtdXRhdGlvbnMgdG8gY3JlYXRlIGEgbnVsbCBtb2RlbCwgYGNhbGN1bGF0ZWAgb3VyIHNhbXBsZSBzdGF0aXN0aWNzIGFuZCB0aGVuIHVzZSB0aGVzZSB0byBkZXRlcm1pbmUgaG93IHN1cnByaXNpbmcgaXQgaXMgdG8gc2VlIG91ciBtZWFzdXJlbWVudC4gV2UgYWxyZWFkeSBoYXZlIGEgaHlwb3RoZXNpcyBmb3JtdWxhdGVkIGFuZCBhbiBpZGVhIG9mIGhvdyB0byBjYWxjdWxhdGUgb3VyIHNhbXBsZSBzdGF0aXN0aWNzLCBzbyB3ZSBzaG91bGQgaGF2ZSBubyBpc3N1ZXMuDQoNCioqWW91ciB0YXNrOioqIEltcGxlbWVudCB0aGUgd29ya2Zsb3cgYWJvdmUuDQoNCjEuICBVc2luZyBgYWxwYWNhc2AsIHNldCB1cCBhbiB7aW5mZXJ9IHBpcGVsaW5lIGJ5IGBzcGVjaWZ5YGluZyB0aGUgdmFyaWFibGVzIGFzIGEgZm9ybXVsYS4gUmVjYWxsIGZyb20gbGFzdCB0aW1lIGhvdyB3ZSB3cml0ZSBmb3JtdWxhcyB3aXRoIHRoZSByZXNwb25zZSBvbiB0aGUgbGVmdCBvZiBhIHRpbGRlIChcfikgYW5kIHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZXMgb24gdGhlIHJpZ2h0Lg0KMi4gIFRoZW4gYGh5cG90aGVzaXplYCB0aGF0IHRoZSB0d28gdmFyaWFibGVzIHlvdSBzcGVjaWZpZWQgYXJlIGBpbmRlcGVuZGVudGANCjMuICBgZ2VuZXJhdGVgIDEwMDAgcGVybXV0YXRpb25zIHVuZGVyIHRoZSBudWxsIG1vZGVsDQo0LiAgYGNhbGN1bGF0ZWAgYSBzYW1wbGUgc3RhdGlzdGljIGZvciBlYWNoIHBlcm11dGF0aW9uLCB3aGljaCBpcyB0aGUgZGlmZmVyZW5jZSBpbiBncm91cCBtZWFucyAoc2VlIGA/Y2FsY3VsYXRlYCBpZiBjb25mdXNlZCkNCjUuICBgdmlzdWFsaXplYCB0aGUgcmVzdWx0aW5nIGRpc3RyaWJ1dGlvbiBvZiB0ZXN0IHN0YXRpc3RpY3MNCg0KYGBge3J9DQojIFRPRE8gWW91ciBjb2RlIGhlcmUNCmBgYA0KDQo6Ojogey5zcG9pbGVyfQ0KYGBge3IgZXZhbD1GQUxTRX0NCmFscGFjYXMgJT4lDQogIHNwZWNpZnkod29vbF9xdWFsaXR5IH4gZ3JvdXApICU+JQ0KICBoeXBvdGhlc2l6ZSgnaW5kZXBlbmRlbmNlJykgJT4lDQogIGdlbmVyYXRlKDEwMDAsICdwZXJtdXRlJykgJT4lDQogIGNhbGN1bGF0ZSgnZGlmZiBpbiBtZWFucycsIG9yZGVyID0gYygndHJlYXRtZW50JywgJ2NvbnRyb2wnKSkgJT4lIHsNCiAgICB2aXN1YWxpemUoLikgKw0KICAgICAgc2hhZGVfY29uZmlkZW5jZV9pbnRlcnZhbChnZXRfY29uZmlkZW5jZV9pbnRlcnZhbCguKSkgKw0KICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gdGVzdF9zdGF0aXN0aWMsIGNvbG9yID0gJ3JlZCcpDQogIH0NCmBgYA0KOjo6DQoNClRoaXMgdmlzdWFsaXphdGlvbiBjYW4gc2hvdyB1cyB0aGF0IGl0IGlzIHVubGlrZWx5IHRvIHJlY2VpdmUgYSB0ZXN0IHN0YXRpc3RpYyBhdCBsZWFzdCBhcyBleHRyZW1lIGFzIHdoYXQgd2Ugc2F3IGJ5IGNoYW5jZSwgb3IgaW4gb3RoZXIgd29yZHMsIHRoZXJlJ3MgYSBsb3cgcHJvYmFiaWxpdHkgdGhhdCB0aGUgb2JzZXJ2ZWQgZGlmZmVyZW5jZSBpbiB3b29sIHF1YWxpdHkgaXMgZHVlIHRvIGNoYW5jZS4gV2UgY2FuIHF1YW50aWZ5IHRoaXMgd2l0aCBhICpwLSp2YWx1ZSBieSB1c2luZyB7aW5mZXJ9J3MgYGdldF9wX3ZhbHVlYCBmdW5jdGlvbi4gU2luY2Ugb3VyIGh5cG90aGVzaXMgaXMgZGlyZWN0aW9uYWwsIHdlIG5lZWQgdG8gc3BlY2lmeSB0aGUgdGFpbCB3ZSBjYXJlIGFib3V0Lg0KDQoqKllvdXIgdGFzazoqKiBJbnN0ZWFkIG9mIHZpc3VhbGl6aW5nIHRoZSBkaXN0cmlidXRpb24gYWJvdmUsIGNhbGN1bGF0ZSBhICpwLSp2YWx1ZSBmb3IgeW91ciBvYnNlcnZlZCBgdGVzdF9zdGF0aXN0aWNgLg0KDQpgYGB7cn0NCiMgVE9ETyBZb3VyIGNvZGUgaGVyZQ0KYGBgDQoNCjo6OiB7LnNwb2lsZXJ9DQpgYGB7ciBldmFsPUZBTFNFfQ0KYWxwYWNhcyAlPiUNCiAgc3BlY2lmeSh3b29sX3F1YWxpdHkgfiBncm91cCkgJT4lDQogIGh5cG90aGVzaXplKCdpbmRlcGVuZGVuY2UnKSAlPiUNCiAgZ2VuZXJhdGUoMTAwMCwgJ3Blcm11dGUnKSAlPiUNCiAgY2FsY3VsYXRlKCdkaWZmIGluIG1lYW5zJywgb3JkZXIgPSBjKCd0cmVhdG1lbnQnLCAnY29udHJvbCcpKSAlPiUNCiAgZ2V0X3BfdmFsdWUodGVzdF9zdGF0aXN0aWMsICdyaWdodCcpDQpgYGANCjo6Og0KDQpPZiBjb3Vyc2UsIHRoZXJlIGFyZSBhIGxhcmdlIG51bWJlciBvZiBvdGhlciBzdGF0aXN0aWNhbCB0ZXN0cyB5b3UgY2FuIHBlcmZvcm0gaW4gUiB0aGF0IGFyZW4ndCBwZXJtdXRhdGlvbiB0ZXN0cywgaW5jbHVkaW5nIGBhbm92YWAsIGBjaGlzcS50ZXN0YCwgYGZpc2hlci50ZXN0YCwgYHQudGVzdGAgYW5kIGV0Yy4gSWYgeW91IGtub3cgdGhlIHRlc3QgZmxhdm9yIHlvdSB3YW50IHRvIHJ1biwgdGhleSdyZSByZWxhdGl2ZWx5IHN0cmFpZ2h0Zm9yd2FyZCB0byBpbXBsZW1lbnQuDQo=